package edu.gatech.cs2340.teamrocket.model;

import java.util.List;
import java.util.LinkedList;
import java.util.Observable;
import java.io.File;
import java.io.Serializable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import edu.gatech.cs2340.teamrocket.model.environment.Environment;
import edu.gatech.cs2340.teamrocket.model.environment.Season;
import edu.gatech.cs2340.teamrocket.model.environment.Time;
import edu.gatech.cs2340.teamrocket.model.environment.Weather;
import edu.gatech.cs2340.teamrocket.model.item.Item;
import edu.gatech.cs2340.teamrocket.model.job.Job;
import edu.gatech.cs2340.teamrocket.model.map.Location;
import edu.gatech.cs2340.teamrocket.model.map.Map;
import edu.gatech.cs2340.teamrocket.model.stats.Stats;
import edu.gatech.cs2340.teamrocket.model.storage.BasicStorage;
import edu.gatech.cs2340.teamrocket.model.storage.Backpack;
import edu.gatech.cs2340.teamrocket.model.storage.ShopInventory;
import edu.gatech.cs2340.teamrocket.model.pokemon.Pokemon;
import edu.gatech.cs2340.teamrocket.model.team.Team;

/**
 * Facade for all model data.
 *
 * @author Joe Tacheron
 */
public class Model extends Observable implements Serializable, ModelInterface {

    /*****************************************
     *          PERSISTENCE SUPPORT          *
     *****************************************/

    /**
     * The file to which to save the game
     */
    public static final String SAVE_FILE
            = System.getProperty("user.home") + "/Documents/CS2340/savegame";

    /*****************************************
     *           SINGLETON SUPPORT           *
     *****************************************/

    private static Model instance;

    public static Model getInstance(){
        if(instance == null)
            instance = new Model();
        return instance;
    }

    public static void setInstance(Model m){
        instance.wagon          = m.wagon;
        instance.map            = m.map;
        instance.environment    = m.environment;
        instance.stats          = m.stats;
        instance.updates        = m.updates;
        instance.setChanged();
        instance.notifyObservers();
    }

    public static void resetInstance(){
        Model newInstance = new Model();
        instance.wagon          = newInstance.wagon;
        instance.map            = newInstance.map;
        instance.environment    = newInstance.environment;
        instance.stats          = newInstance.stats;
        instance.updates        = new LinkedList<String>();
        instance.setChanged();
        instance.notifyObservers();
    }

    /*****************************************
     *                FIELDS                 *
     *****************************************/

    private Team wagon;
    private Map map;
    private Environment environment;
    private Stats stats;
    private List<String> updates;

    /*****************************************
     *              CONSTRUCTOR              *
     *****************************************/

    private Model(){
        wagon = new Team();
        map = new Map();
        environment = new Environment();
        stats = new Stats();
        updates = new LinkedList<String>();
    }

    /*****************************************
     *              PERSISTENCE              *
     *****************************************/

    /**
     * Load the game from the default directory.
     *
     * @throws IOException The save file is corrupt
     */
    public void loadGameData() throws IOException, FileNotFoundException {
        try{
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(SAVE_FILE));
            Model model = (Model)in.readObject();
            Model.setInstance(model);
        }
        // this should not be thrown
        catch(ClassNotFoundException e){
            throw new IOException("Could not load game data from " + SAVE_FILE);
        }
    }

    /**
     * Save the game to the default save file.
     *
     * @throws IOException
     */
    public void saveGameData() throws IOException {
        new File(SAVE_FILE).getParentFile().mkdirs();
        new File(SAVE_FILE).createNewFile();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(SAVE_FILE));
        out.writeObject(this);
        out.flush();
        out.close();
    }

    /**
     * Update the server with the game data.
     *
     * @throws IOException
     */
    public void updateServer() throws IOException {

    }

    /*****************************************
     *                SETTERS                *
     *****************************************/

    // Team

    @Override
    public void namePokemon(int i, String name){
        wagon.namePokemon(i, name);
    }

    @Override
    public void setTeamSpeed(int speed){
        wagon.setSpeed(speed);
    }

    @Override
    public void setTeamRations(int rations){
        wagon.setRations(rations);
    }

    @Override
    public void nameTrainer(String n){
        wagon.namePlayer(n);
    }

    @Override
    public void setTrainerJob(Job j){
        wagon.setJob(j);
    }

    // Stats

    @Override
    public void incTurn(){
        stats.incTurn();
    }

    @Override
    public void incScore(long amount){
        stats.incScore(amount);
    }

    @Override
    public void setIP(String ip){
        stats.setIP(ip);
    }

    @Override
    public void setUsername(String user){
        stats.setUsername(user);
    }

    @Override
    public void setPassword(String pass){
        stats.setPassword(pass);
    }

    // Environment

    @Override
    public void incDay(int step){
        environment.incDay(step);
    }

    @Override
    public void changeWeather(){
        environment.changeWeather();
    }

    // Map

    @Override
    public void setDistTravel(int distTravel){
        map.setDistTravel(distTravel);
    }

    // Inventory

    @Override
    public void setInventory(BasicStorage bs){
        wagon.getInventory().become(bs); // TODO: LoD
    }

    @Override
    public void removeItem(Item i){
        wagon.getInventory().removeItem(i); // TODO: LoD
    }

    @Override
    public void removeItem(Item i, int q){
        wagon.getInventory().removeItem(i, q); // TODO: LoD
    }

    @Override
    public void deductMoney(int amount){
        wagon.getInventory().deductMoney(amount);
    }

    @Override
    public void clearInventory(){
        wagon.getInventory().clear();
    }

    // updates

    @Override
    public void addUpdates(List<String> list){
        updates.addAll(list);
    }

    /*****************************************
     *                GETTERS                *
     *****************************************/

    // Team

    @Override
    public List<Pokemon> getPokemon(){
        return wagon.getPokemon();
    }

    @Override
    public int getTeamSpeed(){
        return wagon.getSpeed();
    }

    @Override
    public int getTeamRations(){
        return wagon.getRations();
    }

    @Override
    public String getTrainerName(){
        return wagon.getName();
    }

    @Override
    public Job getTrainerJob(){
        return wagon.getJob();
    }

    @Override
    public boolean teamIsFainted(){
        return wagon.teamIsFainted();
    }

    @Override
    public String wagonToHTML(){
        return wagon.toHTML();
    }

    // Stats

    @Override
    public int getTurn(){
        return stats.getTurn();
    }

    @Override
    public long getScore(){
        return stats.getScore();
    }

    @Override
    public String getIP(){
        return stats.getIP();
    }

    @Override
    public String getUsername(){
        return stats.getUsername();
    }

    @Override
    public String getPassword(){
        return stats.getPassword();
    }

    @Override
    public String statsToHTML(){
        return stats.toHTML();
    }

    //Environment

    @Override
    public int getDay(){
        return environment.getDay();
    }

    @Override
    public Season getSeason(){
        return environment.getSeason();
    }

    @Override
    public Weather getWeather(){
        return environment.getWeather();
    }

    @Override
    public Time getTime(){
        return environment.getTime();
    }

    @Override
    public int getMaxWagonSpeed(){
        return environment.maxWagonSpeed();
    }

    @Override
    public String environmentToHTML(){
        return environment.toHTML();
    }

    // Map

    @Override
    public Location moveForward(int dist){
        return map.moveForward(dist);
    }

    @Override
    public boolean isAtEndOfMap(){
        return map.atEnd();
    }

    @Override
    public int getDistanceTraveled(){
        return map.getDistTravel();
    }

    @Override
    public Location getCurrentLocation(){
        return map.getCurrentLocation();
    }

    @Override
    public boolean hasLocalShop(){
        return map.hasLocalShop();
    }

    @Override
    public ShopInventory getLocalShop(){
        return map.getLocalShop();
    }

    @Override
    public String mapToHTML(){
        return map.toHTML();
    }

    // Inventory

    @Override
    public int getBackpackQuantity(Item i){
        return wagon.getInventory().getQuantity(i); // TODO: LoD
    }

    @Override
    public boolean backpackContains(Item i){
        return wagon.getInventory().contains(i); // TODO: LoD
    }

    @Override
    public double getBackpackMoney(){
        return wagon.getInventory().getMoney(); // TODO: LoD
    }

    @Override
    public double getBackpackAvailableWeight(){
        return wagon.getInventory().getAvailableWeight(); // TODO: LoD
    }

    @Override
    public Backpack getBackpack(){
        return wagon.getInventory();
    }

    @Override
    public BasicStorage cloneBackpack(){
        return wagon.getInventory().clone(); // TODO: LoD
    }

    @Override
    public String backpackToHTML(){
        return wagon.getInventory().toHTML(); // TODO: LoD
    }

    @Override
    public List<Location> getAllLocations(){
    	return map.getLocations();
    }

    // updates

    @Override
    public List<String> getUpdates(){
        return updates;
    }
}
